package cn.cloudtogo.common.util;

import java.util.function.Supplier;

public class RetryHelper {

    /**
     * 执行指定的操作，操作失败时可以进行重试。如果达到最大重试次数后仍然失败，就返回null值
     *
     * @param action     可能需要重试的操作。当操作执行成功时，允许有一个期望的返回值；
     *                   如果执行失败，可以通过抛出{@linkplain RetryException}来告知失败，从而触发重试
     * @param maxRetries 最大重试次数
     * @param interval   重试时间间隔，单位为ms
     * @param <T>        返回数据的类型
     * @return 操作执行成功后，返回期望值；如果达到最大重试次数后仍然失败，则返回null
     */
    public static <T> T mayRetry(Supplier<T> action, int maxRetries, int interval) throws RetryException {
        return mayRetry(action, () -> null, maxRetries, interval);
    }

    /**
     * 执行指定的操作，操作失败时可以进行重试
     *
     * @param action     可能需要重试的操作。当操作执行成功时，允许有一个期望的返回值；
     *                   如果执行失败，可以通过抛出{@linkplain RetryException}来告知失败，从而触发重试
     * @param maxRetries 最大重试次数
     * @param interval   重试时间间隔，单位为ms
     * @param <T>        返回数据的类型
     * @return 操作执行成功后，返回期望值；如果达到最大重试次数后仍然失败，否则抛出RetryException
     * @throws RetryException 当达到最大重试次数后，如果仍然失败，就抛出RetryException
     */
    public static <T> T mayRetryExceptional(Supplier<T> action, int maxRetries, int interval) throws RetryException {
        return mayRetry(action, null, maxRetries, interval);
    }

    /**
     * @param action     可能需要重试的操作。当操作执行成功时，允许有一个期望的返回值；
     *                   如果执行失败，可以通过抛出{@linkplain RetryException}来告知失败，从而触发重试
     * @param fallback   达到最大重试次数仍失败时执行的补偿操作
     * @param maxRetries 最大重试次数
     * @param interval   重试时间间隔，单位为ms
     * @param <T>        返回数据的类型
     * @return action执行成功时，返回action执行的返回值。
     * 如果达到最大重试次数后仍然失败，当fallback不为null时，返回fallback执行的结果；当fallback为null时，抛出最后一次action执行过程中出现的异常
     */
    public static <T> T mayRetry(Supplier<T> action, Supplier<T> fallback, int maxRetries, int interval) {
        Exception ex;
        int retries = 0;
        do {
            retries++;
            try {
                return action.get();
            } catch (Exception e) {
                wait(interval);
                ex = e;
            }
            if (retries < maxRetries) {
                wait(interval);
            }
        } while (retries < maxRetries);
        if (fallback != null) {
            return fallback.get();
        }
        throw new RetryException("重试达到最大次数", ex);
    }

    private static void wait(int milliseconds) {
        try {
            Thread.sleep(milliseconds);
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }
    }

    public static class RetryException extends RuntimeException {

        public RetryException(String message) {
            super(message);
        }

        public RetryException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}
